﻿using System;
using System.Collections.Generic;
using System.Linq;
using Microsoft.Xna.Framework;
using Microsoft.Xna.Framework.Audio;
using Microsoft.Xna.Framework.Content;
using Microsoft.Xna.Framework.GamerServices;
using Microsoft.Xna.Framework.Graphics;
using Microsoft.Xna.Framework.Input;
using Microsoft.Xna.Framework.Media;
using Microsoft.Xna.Framework.Net;
using Microsoft.Xna.Framework.Storage;
using ProceduralGolf.generation;

namespace ProceduralGolf.terrain
{
    class BinaryTriangleTerrain
    {
        BinaryTriangleTree[,] trees;

        public BinaryTriangleTree[,] Trees { get { return trees; } }

        const int patchSize = 256;

        GraphicsDevice graphicsDevice;

        public Heightmap Heightmap { get { return heightmap; } }
        Heightmap heightmap;
        GolfCourseLayout.GolfCourseLayer[,] layermap;

        public float Scale { get { return scale; } }
        float scale;

        public BinaryTriangleTerrain(Game game, GolfCourse course)
        {
            this.heightmap = course.Heightmap;
            this.layermap = course.Layermap;

            this.scale = course.Scale;

            int width = (heightmap.Width - 1) / patchSize;
            int height = (heightmap.Height - 1) / patchSize;

            //Vector3[,] normalmap = HeightmapToNormalmap(heightmap);

            trees = new BinaryTriangleTree[width, height];
            for (int x = 0; x < width; ++x)
            {
                for (int y = 0; y < height; ++y)
                {
                    Heightmap hmap = GetHeightmapPart(heightmap, patchSize + 1, x * patchSize, y * patchSize);
                    //Vector3[,] nmap = GetNormalmapPart(normalmap, patchSize + 1, x * patchSize, y * patchSize);
                    trees[x, y] = new BinaryTriangleTree(hmap, scale, x*patchSize, y*patchSize);
                }
            }
            AdvancedTerrainShaderGenerator shaderGen = new AdvancedTerrainShaderGenerator(game);
            SetEffect(shaderGen.GetEffect(course));
        }

        void UpdateHeightmap()
        {
            for (int x = 0; x < heightmap.Width-1; ++x)
            {
                for (int y = 0; y < heightmap.Height-1; ++y)
                {
                    //float oldHeight = heightmap.getHeight(x, y);
                    float newHeight = GetHeight(x, y);
                    heightmap.setHeight(x, y, newHeight);
                }
            }
        }

        float GetHeight(int x, int y)
        {
            int xx = x / patchSize;
            int yy = y / patchSize;

            int i = x % patchSize;
            int j = y % patchSize;

            if (i == 0 && x > 0)
            {
                --xx;
                i = patchSize;
            }
            if (j == 0 && y > 0)
            {
                --yy;
                j = patchSize;
            }

            return trees[xx, yy].GetHeight(i, j);
        }

        //public void AddEffect(Effect effect, GolfCourseLayout.GolfCourseLayer layer)
        //{
        //    foreach (BinaryTriangleTree tree in trees)
        //    {
        //        tree.AddEffect(effect, layer);
        //    }
        //}

        public void SetEffect(Effect effect)
        {
            foreach (BinaryTriangleTree tree in trees)
            {
                tree.SetEffect(effect);
            }
        }

        public void Generate(GraphicsDevice graphicsDevice, float maxVariance, Vector3 cameraPosition)
        {
            this.graphicsDevice = graphicsDevice;
            foreach (BinaryTriangleTree tree in trees)
            {
                tree.GenerateRoot();
            }
            for (int x = 0; x < trees.GetLength(0); ++x)
            {
                for (int y = 0; y < trees.GetLength(1); ++y)
                {
                    if (x < trees.GetLength(0) - 1)
                    {
                        HorizontalConnectTrees(trees[x, y], trees[x + 1, y]);
                    }
                    if (y < trees.GetLength(1) - 1)
                    {
                        VerticalConnectTrees(trees[x, y], trees[x, y + 1]);
                    }
                }
            }
            foreach (BinaryTriangleTree tree in trees)
            {
                tree.Generate(maxVariance, cameraPosition);
            }

            UpdateHeightmap();
            Vector3[,] normalmap = HeightmapToNormalmap(heightmap);

            int width = (heightmap.Width - 1) / patchSize;
            int height = (heightmap.Height - 1) / patchSize;
            for (int x = 0; x < width; ++x)
            {
                for (int y = 0; y < height; ++y)
                {
                    Vector3[,] nmap = GetNormalmapPart(normalmap, patchSize + 1, x * patchSize, y * patchSize);
                    trees[x, y].SetNormalMap(nmap);
                }
            }

            foreach (BinaryTriangleTree tree in trees)
            {
                tree.GenerateBuffers(graphicsDevice);
            }

        }

        public void Draw(Matrix world, Matrix view, Matrix projection, Vector3 viewVector)
        {
            foreach (BinaryTriangleTree tree in trees)
            {
                tree.Draw(graphicsDevice, world, view, projection, viewVector);
            }
        }

        Heightmap GetHeightmapPart(Heightmap heightmap, int size, int offsetX, int offsetY)
        {
            int width = heightmap.Width;
            int height = heightmap.Height;

            float[,] heightmapPart = new float[size, size];
            for (int x = 0; x < size; ++x)
            {
                for (int y = 0; y < size; ++y)
                {
                    if (x + offsetX < width && y + offsetY < height)
                    {
                        heightmapPart[x, y] = heightmap.getHeight(x + offsetX, y + offsetY);
                    }
                }
            }
            return new Heightmap(heightmapPart);
        }

        Vector3[,] GetNormalmapPart(Vector3[,] normalmap, int size, int offsetX, int offsetY)
        {
            int width = normalmap.GetLength(0);
            int height = normalmap.GetLength(1);

            Vector3[,] normalmapPart = new Vector3[size, size];
            for (int x = 0; x < size; ++x)
            {
                for (int y = 0; y < size; ++y)
                {
                    if (x + offsetX < width && y + offsetY < height)
                    {
                        normalmapPart[x, y] = normalmap[x + offsetX, y + offsetY];
                    }
                }
            }
            return normalmapPart;
        }

        GolfCourseLayout.GolfCourseLayer[,] GetLayermapPart(GolfCourseLayout.GolfCourseLayer[,] layermap, int size, int offsetX, int offsetY)
        {
            int width = layermap.GetLength(0);
            int height = layermap.GetLength(1);

            GolfCourseLayout.GolfCourseLayer[,] layermapPart = new GolfCourseLayout.GolfCourseLayer[size, size];
            for (int x = 0; x < size; ++x)
            {
                for (int y = 0; y < size; ++y)
                {
                    if (x + offsetX < width && y + offsetY < height)
                    {
                        layermapPart[x, y] = layermap[x + offsetX, y + offsetY];
                    }
                }
            }
            return layermapPart;
        }

        void VerticalConnectTrees(BinaryTriangleTree up, BinaryTriangleTree down)
        {
            if (up.LeftRoot == null)
            {
                up.GenerateRoot();
            }
            if (down.LeftRoot == null)
            {
                down.GenerateRoot();
            }
            up.RightRoot.RightNeighbor = down.LeftRoot;
            down.LeftRoot.RightNeighbor = up.RightRoot;
        }

        void HorizontalConnectTrees(BinaryTriangleTree left, BinaryTriangleTree right)
        {
            if (left.LeftRoot == null)
            {
                left.GenerateRoot();
            }
            if (right.LeftRoot == null)
            {
                right.GenerateRoot();
            }
            left.RightRoot.LeftNeighbor = right.LeftRoot;
            right.LeftRoot.LeftNeighbor = left.RightRoot;
        }

        Vector3[,] HeightmapToNormalmap(Heightmap heightmap)
        {
            int width = heightmap.Width;
            int height = heightmap.Height;
            Vector3[,] normalmap = new Vector3[width, height];

            for (int x = 0; x < width; ++x)
            {
                for (int y = 0; y < height; ++y)
                {
                    float sx = heightmap.getHeight((x < width-1 ? x+1 : x), y) - heightmap.getHeight((x > 0 ? x-1 : x), y);
                    if (x == 0 || x == width - 1)
                    {
                        sx *= 2;
                    }
                    float sy = heightmap.getHeight(x, (y < height-1 ? y+1 : y)) - heightmap.getHeight(x, (y > 0 ? y-1 : y));
                    if (y == 0 || y == height - 1)
                    {
                        sy *= 2;
                    }

                    Vector3 normal = new Vector3(-sx, 2.0f, sy);
                    normal.Normalize();
                    normalmap[x, y] = normal;
                }
            }

            return normalmap;
        }

        public int CountTriangles()
        {
            int sum = 0;
            foreach (BinaryTriangleTree tree in trees)
            {
                sum += tree.CountTriangles();
            }
            return sum;
        }

    }
}
